import { Nullable } from "@babylonjs/core/types";
import { BaseTexture } from "@babylonjs/core/Materials/Textures/baseTexture";
import { Constants } from "@babylonjs/core/Engines/constants";
import { ThinEngine } from "@babylonjs/core/Engines/thinEngine";
import { PrecisionDate } from "@babylonjs/core/Misc/precisionDate";
import { EffectWrapper, EffectRenderer } from "@babylonjs/core/Materials/effectRenderer";
import { InternalTexture } from "@babylonjs/core/Materials/Textures/internalTexture";
import { RenderTargetWrapper } from "@babylonjs/core/Engines/renderTargetWrapper";
import { GifShaderConfiguration } from "./gif-texture-shader";
import { parseGIF, decompressFrames } from "gifuct-js";
import "@babylonjs/core/Engines/Extensions/engine.rawTexture";


/**
 * gifuct-js库没有类型声明文件，此类型用以补充
 */
declare type GifFrame = {
    /**
     * 当前帧尺寸
     */
    dims: {
        width: number,
        height: number,
        top: number,
        left: number,
    },
    /**
     * RGBA格式的当前帧数据
     */
    patch: Uint8ClampedArray,
    /**
     * 当前帧的显示时长
     */
    delay: number,
    /**
     * 当前帧的关联纹理
     */
    texture: InternalTexture;
    /**
     * 当前gif补丁缩放平移的变换矩阵
     */
    worldMatrix: Float32Array;
    /**
     * 指示切换到下一帧时数据的处理方式：值为 2 表示丢弃，值为 3 表示不支持，其余值表示保留
     */
    disposalType: number;
};

/**
 * gif纹理选项
 * @property flipY 是否垂直翻转纹理
 * @property url 纹理地址
 */
interface IGifTextureOptions {
    flipY?: boolean;
    url?: string;
}

/**
 * gif动图纹理
 * @param url 纹理地址
 * @param engine 渲染引擎
 * @param onLoad 实例化完成时的回调函数
 */
class GifTexture extends BaseTexture {
    private _onLoad: Nullable<() => void>

    private _frames: Nullable<GifFrame[]> = null;
    private _currentFrame: Nullable<GifFrame> = null;
    private _previousFrame: Nullable<GifFrame> = null;
    private _nextFrameIndex = 0;
    private _previousDate: number = 0;

    private _patchEffectWrapper!: EffectWrapper;
    private _patchEffectRenderer!: EffectRenderer;
    private _renderLoopCallback!: () => void;

    private _renderTarget?: RenderTargetWrapper;

    private _options: IGifTextureOptions;

    constructor(name: string, engine: ThinEngine, options: IGifTextureOptions = {}, onLoad: Nullable<() => void> = null) {
        super(engine);
        this._options = options;
        this.name = name;
        this._onLoad = onLoad;

        this._createInternalTexture();
        this._createRenderer();
        this._createRenderLoopCallback();
        if (this._options.url) this._loadGifTexture();
    }

    /**
     * 创建内部纹理
     */
    private _createInternalTexture(): void {
        this._texture = this._engine!.createRawTexture(null, 1, 1, Constants.TEXTUREFORMAT_RGBA, false, false,
            Constants.TEXTURE_BILINEAR_SAMPLINGMODE, null, Constants.TEXTURETYPE_UNSIGNED_BYTE);

        // 标记数据载入未完成
        this._texture.isReady = false;

        // 兼容webGL1
        this.wrapU = Constants.TEXTURE_CLAMP_ADDRESSMODE;
        this.wrapV = Constants.TEXTURE_CLAMP_ADDRESSMODE;
        this.wrapR = Constants.TEXTURE_CLAMP_ADDRESSMODE;
        this.anisotropicFilteringLevel = 1;
    }

    /**
     * 创建绘制gif补丁的渲染器资源
     */
    private _createRenderer(): void {
        // 封装自定义着色器
        this._patchEffectWrapper = new EffectWrapper({
            ...GifShaderConfiguration,
            engine: this._engine!,
        });

        // 创建帧位块传输的全屏渲染器
        this._patchEffectRenderer = new EffectRenderer(this._engine!, {
            positions: [1, 1, 0, 1, 0, 0, 1, 0]
        });
    }

    /**
     * 渲染循环回调函数
     */
    private _createRenderLoopCallback(): void {
        this._renderLoopCallback = () => {
            this._renderFrame();
        };
    }

    /**
     * 加载gif数据
     */
    private _loadGifTexture(): void {
        // 读取数据后的处理函数
        const callback = (buffer: ArrayBuffer) => {
            this._parseGifData(buffer);
            this._createGifResources();

            // 渲染帧队列
            this._engine!.runRenderLoop(this._renderLoopCallback!);
        };

        // 从gif文件加载数组缓冲区
        this._engine!._loadFile(this._options.url!, callback as { (bfr: ArrayBuffer | string): void }, undefined, undefined, true);
    }

    /**
     * 从数据创建gif纹理
     * @param buffer 储存数据的缓冲区
     */
    public fromData(buffer: ArrayBuffer): GifTexture {
        this._parseGifData(buffer);
        this._createGifResources();

        // 渲染帧队列
        this._engine!.runRenderLoop(this._renderLoopCallback!);
        return this;
    }

    /**
     * 转换gif数据并创建关联帧
     * @param buffer 储存数据的缓冲区
     */
    private _parseGifData(buffer: ArrayBuffer): void {
        const gifData = parseGIF(buffer);
        this._frames = decompressFrames(gifData, true) as unknown as GifFrame[];
    }

    /**
     * 创建gif文件关联的gpu资源（创建所有帧纹理以及用于保存最终gif的渲染对象）
     */
    private _createGifResources(): void {
        const frames = this._frames!;
        const texture = this._texture!;
        for (let frame of frames!) {
            // 创建帧的专用纹理（仅包含图像部分的补丁数据）
            frame.texture = this._engine!.createRawTexture(new Uint8Array(frame.patch.buffer),
                frame.dims.width,
                frame.dims.height,
                Constants.TEXTUREFORMAT_RGBA,
                false,
                true,
                Constants.TEXTURE_NEAREST_SAMPLINGMODE,
                null,
                Constants.TEXTURETYPE_UNSIGNED_BYTE);

            // 由于只包含图像部分内容，这里需要变换渲染的补丁，以适配文件的位置数据
            const sx = frame.dims.width / frames[0].dims.width;
            const sy = frame.dims.height / frames[0].dims.height;
            const tx = frame.dims.left / frames[0].dims.width;
            // 从底部开始渲染，使用相应的变换计算
            const ty = (frames[0].dims.height - (frame.dims.top + frame.dims.height)) / frames[0].dims.height;
            frame.worldMatrix = new Float32Array([
                sx, 0, tx,
                0, sy, ty,
                0, 0, 1,
            ]);

            // 兼容webgl1
            this._engine!.updateTextureWrappingMode(frame.texture, Constants.TEXTURE_CLAMP_ADDRESSMODE, Constants.TEXTURE_CLAMP_ADDRESSMODE);
        }

        // 基于gif尺寸创建主要渲染对象 
        this._renderTarget = this._engine!.createRenderTargetTexture(frames[0].dims, {
            format: Constants.TEXTUREFORMAT_RGBA,
            generateDepthBuffer: false,
            generateMipMaps: false,
            generateStencilBuffer: false,
            samplingMode: Constants.TEXTURE_BILINEAR_SAMPLINGMODE,
            type: Constants.TEXTURETYPE_UNSIGNED_BYTE
        });

        // 释放当前内部纹理资源
        this._engine!._releaseTexture(texture!);

        // 交换内部纹理数据到新的渲染对象
        this._renderTarget.texture!._swapAndDie(texture!);

        // 适配数据
        this._engine!.updateTextureWrappingMode(texture!, Constants.TEXTURE_CLAMP_ADDRESSMODE, Constants.TEXTURE_CLAMP_ADDRESSMODE);
        texture.width = frames[0].dims.width;
        texture.height = frames[0].dims.height;
        texture.isReady = false;
    }

    /**
     * 就绪后渲染当前帧
     */
    private _renderFrame(): void {
        // 根据gif数据中的时长保持当前帧
        if (this._currentFrame && (PrecisionDate.Now - this._previousDate) < this._currentFrame.delay) {
            return;
        }

        const frames = this._frames!;
        // 替换当前帧
        this._currentFrame = frames[this._nextFrameIndex];

        // 绘制补丁
        this._drawPatch();

        // 记录当前帧的绘制时间
        this._previousDate = PrecisionDate.Now;

        // 更新下一帧索引
        this._nextFrameIndex++;
        if (this._nextFrameIndex >= frames.length) {
            this._nextFrameIndex = 0;
        }
    }

    /**
     * 在前一图像上继续绘制新的补丁纹理
     */
    private _drawPatch(): void {
        if (!this._patchEffectWrapper.effect.isReady()) {
            return;
        }

        // 当前帧
        const frame: GifFrame = this._currentFrame!;

        // 记录原视口
        const oldViewPort = this._engine!.currentViewport;

        // 如果 GIF 数据中要求，则清除上一帧
        if (this._previousFrame && (this._previousFrame.disposalType === 2 || this._nextFrameIndex === 0)) {
            // 渲染时将特制的输入参数应用到该效果
            this._patchEffectWrapper.onApplyObservable.addOnce(() => {
                this._patchEffectWrapper.effect.setBool("flipY", !!this._options.flipY);
                this._patchEffectWrapper.effect.setFloat4("color", 0, 0, 0, 0);
                this._patchEffectWrapper.effect.setMatrix3x3("world", this._previousFrame!.worldMatrix);
                this._patchEffectWrapper.effect._bindTexture("textureSampler", this._previousFrame!.texture);
            });

            this._patchEffectRenderer.render(this._patchEffectWrapper, this._renderTarget);
        }

        // 应用输入参数
        this._patchEffectWrapper.onApplyObservable.addOnce(() => {
            this._patchEffectWrapper.effect.setBool("flipY", !!this._options.flipY);
            this._patchEffectWrapper.effect.setFloat4("color", 1, 1, 1, 1);
            this._patchEffectWrapper.effect.setMatrix3x3("world", frame.worldMatrix);
            this._patchEffectWrapper.effect._bindTexture("textureSampler", frame.texture);
        });

        // 在前一帧上继续渲染当前帧
        this._patchEffectRenderer.render(this._patchEffectWrapper, this._renderTarget);

        // 保存销毁类型，以备下次更新使用
        this._previousFrame = frame;

        // 重设为旧视口
        this._engine!.setViewport(oldViewPort!);

        if (!this._texture!.isReady) {
            this._texture!.isReady = true;
            this._onLoad && this._onLoad();
        }
    }
    /**
     * 销毁纹理，释放相关资源
     */
    public dispose(): void {
        // 停止gif更新循环
        this._engine!.stopRenderLoop(this._renderLoopCallback);

        // 清除渲染辅助实例
        this._patchEffectWrapper.dispose();
        this._patchEffectRenderer.dispose();

        // 销毁纹理
        for (let frame of this._frames!) {
            frame.texture.dispose();
        }

        this._renderTarget!.dispose();

        // 销毁渲染对象相关资源
        super.dispose();
    }
}


export { GifTexture };
export type { IGifTextureOptions };